package top.cardone.web.servlet;

import com.google.common.collect.Lists;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.ModelAndView;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.util.CodeExceptionUtils;
import top.cardone.context.util.StringUtils;
import top.cardone.context.util.TableUtils;
import top.cardone.core.CodeException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author yao hai tao
 * @date 2016/1/20
 */
@Log4j2
public class HandlerExceptionResolver implements org.springframework.web.servlet.HandlerExceptionResolver {
    @Value("${server.error.path:${error.path:/error}}")
    private final String errorViewName = "/error";

    @Setter
    private int exceptionUpperLimitQuantity = 5000;

    @Setter
    private List<String> notJsonMediaTypeUrls = Lists.newArrayList("/**/*.html");

    @Setter
    private String jsonpCallbackParam = "callback";

    @Setter
    private String taskExecutorBeanName = "slowTaskExecutor";

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (!(ex instanceof CodeException)) {
            ApplicationContextHolder.getBean(AsyncListenableTaskExecutor.class, this.taskExecutorBeanName).submitListenable(() -> {
                log.error(ex.getMessage(), ex);

                Long count = TableUtils.longAdderIncrementGetSum(this.getClass().getName(), "resolveException");

                if (count > exceptionUpperLimitQuantity) {
                    ((Runnable) () -> {
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            log.error(e.getMessage(), e);
                        }

                        System.exit(0);
                    }).run();
                }
            });
        }

        String contentType = org.apache.commons.lang3.StringUtils.defaultString(request.getContentType(), request.getHeader("Accept"));

        if (!StringUtils.matchs(notJsonMediaTypeUrls, request.getServletPath()) &&
                StringUtils.startsWith(contentType, MediaType.APPLICATION_JSON_VALUE)) {
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);

            try (Writer out = response.getWriter()) {
                String json = CodeExceptionUtils.newString(request.getServletPath(), ex);

                String callback = request.getParameter(jsonpCallbackParam);

                if (StringUtils.isBlank(callback)) {
                    out.write(json);
                } else {
                    out.write(StringUtils.join(callback, "(", json, ")"));
                }

                out.flush();
            } catch (java.io.IOException e) {
                log.error(e.getMessage(), e);
            }

            return null;
        }

        ModelAndView model = new ModelAndView(this.errorViewName);

        Map<String, String> errorInfoMap = CodeExceptionUtils.newMap(request.getServletPath(), ex);

        model.addAllObjects(errorInfoMap);

        return model;
    }
}
